/*
 * SoftLogPool.java	2009/03/10
 *
 * Copyright 2009 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;

/**
 * This implementation of LogPool use the mechanism supplied by the SoftReference. 
 * Logs in this pool are clean as soon as they are not soft referenced by anyone and the JVM
 * needs to gain memory space. <br/>
 * 
 * <p>The cleaning is done by calling the following method:
  * <li>
 * <ul>by calling method clearPool() any SoftReference or Log with Status different from RUNNING is removed from Queue</ul>
 * </li>  
 * <p>
 * These methods also perform a LogPool cleaning:
 * <li> 
 * <ul>size(): call clearPool() before returning the size</ul>
 * <ul>findClassByName(): clear pool if Log is SoftReferenced or with status different from RUNNING</ul>
 * </li>
 * </p> 
 * @author Leonardo Celati
 */
final class SoftLogPool<E extends Log> extends LogPool<E> {
	
	/** As required by serialization */
	private static final long serialVersionUID = -614299665505461583L;
	/** Storage for our log */
	private final Map<String,SoftLog<E>> queue;	
	/** The reference Queue */
	transient private ReferenceQueue<E> rq;
	
	/** Default constructor */
	public SoftLogPool(Properties p) {
		super(p);
		// initializing object...
		queue = new HashMap<String,SoftLog<E>>();
		rq = new ReferenceQueue<E>();
		
		// everything went fine...
		this.setStatus(Status.RUNNING);
	}	

	/**
	 * Add a log to pool 
	 * @param e Log to add
	 * @return true/false if been added
	 */
	@Override
	boolean add(E e) {
		
		// cannot add null Log or Log with status other than running... 
		if (e==null || e.getStatus() != Status.RUNNING) {
			return false;
		}
		
		try {
			// adding to Map then to
			// soft reference...
			synchronized(queue) { 
				queue.put(e.getClassname(), new SoftLog<E>(e,rq));
				queue.notifyAll();
				return true;
			}
		} catch (Exception x) {
			return false;
		}
	}
	
	/**
	 * Find a log by its name
	 * @param classname the name to look for
	 * @return a valid log if found
	 */
	@Override
	protected final E findByClassname(String classname) {		
	
		// ask the pool for our SoftLog...
		// and check first if it's not null
		SoftLog<E> wl;
		E e = null;
		if ((wl = queue.get(classname)) != null ) {
			
			// Log may have been removed by the jvm
			// because it is wrapped in a SoftReference
			// in that case we try to remove from the Queue
			// we also remove anything that is not in running state			
			e =(E)wl.get();
			// removing anything that is not running...
			if (e==null || e.getStatus() != Status.RUNNING) {
				synchronized (queue) {
					queue.remove(wl.key);
				}
			}	
		}
		// returning our log...
		return e;		
	}
	
	/**
	 * Create a log with a specific name or return if exists
	 * @param classname the name of the log
	 * @return a valid log
	 */
	@Override
	E getInstance(String classname) {
		// find and return if found...
		// otherwise create it...
		E e;
		// look in the pool...		
		if ((e=this.findByClassname(classname)) !=null) {
			return e;
		}
		
		// create, store and return...
		e=this.createLog(classname);
		this.add(e);
		
		return e;
	}
	
	/**
	 * Get the size of the pool
	 * @return the pool size
	 */
	@Override
	public int size() {
		if (queue != null) {
			// clear pool first...
			this.clearPool();
			return queue.size();
		} else 
			return -1;
	}
	
	/**
	 * Shutdown this LogPool
	 */
	public void kill() {
		// clean and empty this pool
			if (queue!=null) {
				synchronized (queue) {
					// set status 
					this.setStatus(Status.SHUTDOWN);
					// clean first...
					this.clearPool();
					// iterate and kill each log...
					Collection<SoftLog<E>> s = queue.values();
					for (SoftLog<E> sl: s) {
						// we already clean pool...
						// so the get should always return a Log...
						sl.get().kill();
					}
					// finally clear pool...
					queue.clear();
					queue.notifyAll();
				}
			}
	}
	

	/** 
	 * Clean our pool from unused Log (SoftReference)
	 */
	public final synchronized void clearPool() {
		// dead keys holder...		 
		List<String> d = new ArrayList<String>();
		
		// get list of keys from map
		// and browsing...
		Set<String> k = queue.keySet();
		
		for (String o : k) {
			// get WeakLog from map then Log
			// if null we add the key to our dead list...
			if (queue.get(o).get() == null ) {
				d.add(o.toString());
			}
		}
		// for each key we remove the 
		// correspondent mapping
		for (String key : d) {
			queue.remove(key);
		}		
	
		// clear...
		k=null;
		d.clear();
		d=null;
		
		// signal to any waiting thread...
		this.notifyAll();		
	}

	/** 
	 * We implement our method for deserializing object because we have to deal with ReferenceQueue
	 * @param is
	 */
	private void readObject(ObjectInputStream is) {
		try {
			is.defaultReadObject();
			rq = new ReferenceQueue<E>();
			System.out.println(queue.size());
			
		} catch (Exception e) { 
			e.printStackTrace(); 
		}
		
	}	
	
	
	/**
	 * This is a classic idiom when we talk about Reference
	 * @author Leonardo Celati
	 */
	private static  class SoftLog<T extends Log>  extends SoftReference<T> {
		// the key (or the classname)
		private final String key;
		/**
		 * Constructor
		 * @param o The Log to store
		 * @param q Our ReferenceQueue
		 */
		public SoftLog(T t, ReferenceQueue<T> q) {
			super(t, q);
			this.key=t.getClassname();
		}
		
		/** Getter for key */
		public String getKey() {
			return this.key;
		}
	}
	
}
